#include "logger.h"

#include <cstdio>

#include <memory>

#include <fmt/chrono.h>

#include "appender.h"
#include "formater_pattern.h"

namespace afcore {
namespace log {

CLogger::CLogger(const CLogger& that)
  : name_(that.name_)
  , appenders_(that.appenders_)
  , level_(that.level_.load(std::memory_order_relaxed))
  , flush_level_(that.flush_level_.load(std::memory_order_relaxed))
  , custom_err_hanlder_(that.custom_err_hanlder_)
  , tracer_(that.tracer_) {
}

CLogger::CLogger(CLogger&& that) noexcept
  : name_(std::move(that.name_))
  , appenders_(std::move(that.appenders_))
  , level_(that.level_.load(std::memory_order_relaxed))
  , flush_level_(that.flush_level_.load(std::memory_order_relaxed))
  , custom_err_hanlder_(std::move(that.custom_err_hanlder_))
  , tracer_(std::move(that.tracer_)) {
}

CLogger& CLogger::operator=(CLogger that) noexcept {
  this->Swap(that);
  return *this;
}

void CLogger::Swap(CLogger& that) noexcept {
  name_.swap(that.name_);
  appenders_.swap(that.appenders_);

  auto level_that = that.level_.load();
  auto level_this = level_.exchange(level_that);
  that.level_.store(level_this);

  level_that = that.flush_level_.load();
  level_this = level_.exchange(level_that);
  that.flush_level_.store(level_this);

  /// @see https://zh.cppreference.com/w/cpp/utility/functional/function/swap
  custom_err_hanlder_.swap(that.custom_err_hanlder_);
  std::swap(tracer_, that.tracer_);
}

void Swap(CLogger& a, CLogger& b) {
  a.Swap(b);
}

void CLogger::SetFormatter(RFormatterUptr formatter) {
  for (auto iter = appenders_.begin(); iter != appenders_.end(); ++iter) {
    if (std::next(iter) == appenders_.end()) {
      /// 最后一个可以移动
      (*iter)->SetFormatter(std::move(formatter));
      break;
    } else {
      (*iter)->SetFormatter(formatter->Clone());
    }
  }
}

void CLogger::SetPattern(std::string pattern, EPatternTimeType time_type /*= EPatternTimeType::kPatternTimeTypeLocal*/) {
  auto formatter = std::make_unique<CFormaterPattern>(std::move(pattern), time_type);
  SetFormatter(std::move(formatter));
}

void CLogger::EnableBacktrace(size_t msg_max) {
  tracer_.Enable(msg_max);
}

void CLogger::DisableBacktrace() {
  tracer_.Disable();
}

void CLogger::DumpBacktrace() {
  DoDumpBacktrace();
}

void CLogger::Flush() {
  DoFlush();
}

RLoggerSptr CLogger::CLone(std::string logger_name) {
  auto cloned = std::make_shared<CLogger>(*this);
  cloned->name_ = std::move(logger_name);
  return cloned;
}

ELogLevel CLogger::GetLogLevel() const {
  return static_cast<ELogLevel>(level_.load(std::memory_order_relaxed));
}

void CLogger::SetLogLevel(ELogLevel log_level) {
  level_.store(log_level);
}

const std::string& CLogger::GetName() const {
  return name_;
}

ELogLevel CLogger::GetFlushLevel() const {
  return static_cast<ELogLevel>(flush_level_.load(std::memory_order_relaxed));
}

void CLogger::SetFlushLevel(ELogLevel l) {
  flush_level_.store(l);
}

const std::vector<RAppenderSptr>& CLogger::GetAppenders() const {
  return appenders_;
}

std::vector<RAppenderSptr>& CLogger::GetAppenders() {
  return appenders_;
}

void CLogger::SetCustomErrHandler(RErrHanlder handler) {
  custom_err_hanlder_ = handler;
}

bool CLogger::ShouldFlush(const SLogMessage& msg) {
  auto flush_level = flush_level_.load(std::memory_order_relaxed);
  return (msg.level >= flush_level) && (msg.level != kLogLevelOff);
}

void CLogger::DoLog(const SLogMessage& log_msg, bool log_enabled, bool traceback_enabled) {
  if (log_enabled) {
    AppenderDoLog(log_msg);
  }

  if (traceback_enabled) {
    tracer_.Append(log_msg);
  }
}

void CLogger::AppenderDoLog(const SLogMessage& msg) {
  for (auto& appender : appenders_) {
    if (appender->ShouldLog(msg.level)) {
      try {
        appender->Log(msg);
      } catch (const std::exception& ex) {
        DoErrHandler(ex.what());
      } catch (...) {
        DoErrHandler("Unknown exception in logger");
      }
    }
  }

  if (ShouldFlush(msg)) {
    DoFlush();
  }
}

void CLogger::DoFlush() {
  for (auto& appender : appenders_) {
    try {
      appender->Flush();
    } catch (const std::exception& ex) {
      DoErrHandler(ex.what());
    } catch (...) {
      DoErrHandler("Unknown exception in logger");
    }
  }
}

void CLogger::DoDumpBacktrace() {
  if (tracer_.Enabled()) {
    AppenderDoLog(SLogMessage{GetName(), kLogLevelInfo, "****************** Backtrace Start ******************"});
    tracer_.ForeachPop(
      [this](const SLogMessage& msg) {
        this->AppenderDoLog(msg);
      });
    AppenderDoLog(SLogMessage{GetName(), kLogLevelInfo, "****************** Backtrace End ********************"});
  }
}

void CLogger::DoErrHandler(const std::string& msg) {
  if (custom_err_hanlder_) {
    custom_err_hanlder_(msg);
  } else {
    static std::mutex mutex;
    static RClockSystem::time_point last_report_time;
    static size_t err_counter = 0;
    std::lock_guard<std::mutex> lock{mutex};
    auto now = RClockSystem::now();
    ++err_counter;
    if (now - last_report_time < RSeconds(1)) {
      return;
    }
    last_report_time = now;
    auto tm_time = fmt::localtime(RClockLog::to_time_t(now));
    char date_buf[64];
    std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time);
    fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, GetName().c_str(), msg.c_str());
  }
}

} // !namespace log

} // !namespace afcore